package com.lecture.junit.tdd.example;


import org.junit.Before;
import org.junit.Test;

import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.*;

public class LogonServiceTest {
	private static final String YURA = "YURA";
	private static final String BRETT = "brett";
	private static final String PASSWORD = "password";
	private static final String NEW_PASSWORD = "newpassword";
	
	LoginService service;
	IAccount account;
	IAccountRepository accountRepository;
	
	@Before
	public void setUp() {
		account = mock(IAccount.class);
        accountRepository = mock(IAccountRepository.class);
        when(account.getUserName()).thenReturn(BRETT);
        when(accountRepository.find(anyString())).thenReturn(account);

        service = new LoginService(accountRepository);
	}
	
	public void willPasswordMatch(boolean match) {
        when(account.passwordMatches(anyString())).thenReturn(match);
	}
	
    @Test
    public void itShouldSetAccountToLoggedInWhenPasswordMatches() throws AccountException {
        willPasswordMatch(true);

        service.login(BRETT, PASSWORD);

        verify(account, times(1)).setLoggedIn();
    }

    @Test
    public void testLogin_shouldRevokeAccount_whenThreeLoginAttemptsFail() throws AccountException {
    	willPasswordMatch(false);

        service.login(BRETT, PASSWORD);
        service.login(BRETT, PASSWORD);
        service.login(BRETT, PASSWORD);

        verify(account, times(1)).setRevoked();
    }
    
    @Test
    public void testLogin_shouldNotSetLoggedIn_whenPasswordNotMatches() throws AccountException {
    	willPasswordMatch(false);

        service.login(BRETT, PASSWORD);

        verify(account, never()).setLoggedIn();
    }
    
    @Test
    public void testLogin_shouldNotSetRevoked_whenOneAccFailsTwoTimesAndOtherFailsOneTime() throws AccountException {
    	willPasswordMatch(false);

    	IAccount second = mock(IAccount.class);
        when(second.getUserName()).thenReturn(YURA);
    	doReturn(false).when(second).passwordMatches(anyString());
        when(accountRepository.find(YURA)).thenReturn(account);
    	
        service.login(BRETT, PASSWORD);
        service.login(BRETT, PASSWORD);

        service.login(YURA, PASSWORD);
        
        verify(account, never()).setRevoked();
    }
    
    @Test(expected = AccountAlreadyLoggedInException.class)
    public void testLogin_shouldNotifyWithException_whenLoggedInAccTriesToLogin() throws AccountException {
    	willPasswordMatch(true);
    	doReturn(true).when(account).isLoggedIn();
    	    	
        service.login(BRETT, PASSWORD);
        service.login(BRETT, PASSWORD);

    }
    
    @Test(expected = AccountNotFoundException.class)
    public void testLogin_shouldThrowException_whenAccIsNotFound() throws AccountException {
    	doReturn(null).when(accountRepository).find(anyString());

        service.login(BRETT, PASSWORD);
    }
    
    @Test(expected = AccountRevokedException.class)
    public void testLogin_shouldNotLoginAndThrowException_whenAccRevoked()throws AccountException {
    	willPasswordMatch(true);
    	doReturn(true).when(account).isRevoked();

        service.login(BRETT, PASSWORD);
    }
    
    @Test
    public void testLogin_shouldRestBackToInitialState_afterSuccessfulLogin() throws AccountException {
    	willPasswordMatch(false);
        service.login(BRETT, PASSWORD);
        service.login(BRETT, PASSWORD);
    	willPasswordMatch(true);
        service.login(BRETT, PASSWORD);
    	willPasswordMatch(false);
        service.login(BRETT, PASSWORD);
        
        verify(account, never()).setRevoked();
    }
    
    @Test(expected = PasswordExpiredException.class)
    public void testLogin_shouldThrowException_whenPasswordTimeExpired() throws AccountException {
    	doReturn(true).when(account).isPasswordExpired();
    	willPasswordMatch(true);
        service.login(BRETT, PASSWORD);
    }
    
    @Test
    public void testLogin_shouldChangePassword_whenPasswordExpiredAndNewPasswordIsValid() throws AccountException {
    	doReturn(true).when(account).isPasswordExpired();
    	doReturn(true).when(accountRepository).isNewPasswordValid(BRETT, NEW_PASSWORD);
    	willPasswordMatch(true);
        try {
			service.login(BRETT, PASSWORD);
		} catch (PasswordExpiredException e) {
			service.login(BRETT, PASSWORD, NEW_PASSWORD);
		}
        
        verify(account, times(1)).setLoggedIn();
    } 

    @Test(expected = InvalidPasswordException.class)
    public void testLogin_shouldThrowException_whenPasswordExpiredAndNewPasswordIsInvalid() throws AccountException {
    	doReturn(true).when(account).isPasswordExpired();
    	doReturn(false).when(accountRepository).isNewPasswordValid(BRETT, NEW_PASSWORD);
    	willPasswordMatch(true);
        
		service.login(BRETT, PASSWORD, NEW_PASSWORD);
    }
    
    @Test(expected = AccountRevokedException.class)
    public void testLogin_shouldThrowException_whenAccountUseTimeExpired() throws AccountException {
    	doReturn(true).when(account).isUsingExpired();
    	doReturn(false).when(account).isPasswordExpired();
    	doReturn(true).when(account).isRevoked();
    	willPasswordMatch(true);
    	service.login(BRETT, PASSWORD);
    }

    @Test
    public void testLogin_shouldLogin_whenAccountUseTimeIsNotExpired() throws AccountException {
    	doReturn(false).when(account).isUsingExpired();
    	doReturn(false).when(account).isPasswordExpired();
    	willPasswordMatch(true);
    	service.login(BRETT, PASSWORD);
    	
    	verify(account, times(1)).setLoggedIn();
    }
}
